﻿using System;
using System.IO;
using System.Linq;
using Dokan;
using DokanNetMirror.MemoryFS;
using MediaAssistant.DAL;
using MediaFS.Data;

namespace MediaFS
{
    public class MemoryFSOperations : IMediaFSOperations
    {
        const string RootFolder = "\\";

        private static MemoryFolder _root;
        public IWaitScreenService WaitScreenService { get; private set; }

        private static MemoryFolder Root
        {
            get { return _root ?? (_root = CreateRoot()); }
            set { _root = value; }
        }

        private static MemoryFolder CreateRoot()
        {
            return new MemoryFolder(null, string.Empty);
        }

        #region DokanOperations members

        public int Cleanup(string filename, DokanFileInfo info)
        {
            return DokanNet.DOKAN_SUCCESS;
        }

        public int CloseFile(string filename, DokanFileInfo info)
        {
            return DokanNet.DOKAN_SUCCESS;
        }

        public int CreateDirectory(string filename, DokanFileInfo info)
        {
            var parentFolderPath = filename.GetPathPart();
            var parentFolder = Root.GetFolderByPath(parentFolderPath);

            if (!parentFolder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            var newName = filename.GetFilenamePart();
            if (newName.IndexOfAny(Path.GetInvalidFileNameChars()) > 0)
            	return -DokanNet.ERROR_INVALID_NAME;
            if (string.IsNullOrEmpty(newName))
                return -DokanNet.ERROR_INVALID_NAME;

            var testFolder = Root.GetFolderByPath(filename);
            if (testFolder.Exists())
                return -DokanNet.ERROR_ALREADY_EXISTS;

            var newFolder = new MemoryFolder(parentFolder, newName);

            return newFolder.Exists() ? DokanNet.DOKAN_SUCCESS : DokanNet.DOKAN_ERROR;
        }

		public int CreateFile(
		    string filename,
		    FileAccess access,
		    FileShare share,
		    FileMode mode,
		    FileOptions options,
		    DokanFileInfo info)
		{
		    if (filename == RootFolder) 
		        return DokanNet.DOKAN_SUCCESS;
		
		    var parentFolder = Root.GetFolderByPath(filename.GetPathPart());
		
		    if (!parentFolder.Exists())
		        return -DokanNet.ERROR_PATH_NOT_FOUND;
		
		    var newName = filename.GetFilenamePart();
		    
		    if (newName.IndexOfAny(Path.GetInvalidFileNameChars()) > 0)
		    	return -DokanNet.ERROR_INVALID_NAME;
		    if (string.IsNullOrEmpty(newName))
		        return -DokanNet.ERROR_INVALID_NAME;            
		
		    var thisFile = (parentFolder.FetchFile(newName));
		
		    var testFolder = Root.GetFolderByPath(filename);
		    if (testFolder.Exists())
		    {
		        info.IsDirectory = true;
		        if (mode == FileMode.Open || mode == FileMode.OpenOrCreate)
		        {
		            return DokanNet.DOKAN_SUCCESS;
		        }
		
		        return -DokanNet.ERROR_ALREADY_EXISTS;
		    }
		
		    switch (mode)
		    {
		        case FileMode.Append:
		            if (!thisFile.Exists())
		                MemoryFile.New(parentFolder, newName);
		            return DokanNet.DOKAN_SUCCESS;
		
		        case FileMode.Create:
		                MemoryFile.New(parentFolder, newName);
		            return DokanNet.DOKAN_SUCCESS;
		
		        case FileMode.CreateNew:
		            if (thisFile.Exists())
		                return -DokanNet.ERROR_ALREADY_EXISTS;
		            MemoryFile.New(parentFolder, newName);
		            return DokanNet.DOKAN_SUCCESS;

                case FileMode.Open:
		            if (!thisFile.Exists())
		                return -DokanNet.ERROR_FILE_NOT_FOUND;
		            return DokanNet.DOKAN_SUCCESS;

		        case FileMode.OpenOrCreate:
		            if (!thisFile.Exists())
		                MemoryFile.New(parentFolder, newName);
		            return DokanNet.DOKAN_SUCCESS;
		
		        case FileMode.Truncate:
		                if (!thisFile.Exists())
		                    thisFile = MemoryFile.New(parentFolder, newName);
		                thisFile.Size = 0;
		                return DokanNet.DOKAN_SUCCESS;
		    }
		
		    return DokanNet.DOKAN_ERROR;
		}
		
        public int DeleteDirectory(string filename, DokanFileInfo info)
        {
            var folder = Root.GetFolderByPath(filename);

            if (!folder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            folder.Parent.Children.Remove(folder);
            return DokanNet.DOKAN_SUCCESS;
        }

        public int DeleteFile(string filename, DokanFileInfo info)
        {
            var parentFolder = Root.GetFolderByPath(
                filename.GetPathPart());

            if (!parentFolder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            var file = parentFolder.FetchFile(
                filename.GetFilenamePart());

            if (!file.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;

            parentFolder.Children.Remove(file);

            return DokanNet.DOKAN_SUCCESS;
        }

        public int FlushFileBuffers(
            string filename,
            DokanFileInfo info)
        {
            return DokanNet.DOKAN_SUCCESS;
        }

        public int FindFiles(
            string filename,
            System.Collections.ArrayList files,
            DokanFileInfo info)
        {
            var folder = filename == RootFolder ? Root : Root.GetFolderByPath(filename);
            if (!folder.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;

            foreach (var item in folder.Children)
            {
                var fileinfo = new FileInformation
                                   {
                                       FileName = item.Name,
                                       Attributes = item.Attributes,
                                       LastAccessTime = item.LastAccessTime,
                                       LastWriteTime = item.LastWriteTime,
                                       CreationTime = item.CreationTime
                                   };

                if (item is MemoryFile)
                	fileinfo.Length = (item as MemoryFile).Size;

                files.Add(fileinfo);
            }
            return DokanNet.DOKAN_SUCCESS;
        }

        public int GetFileInformation(
            string filename,
            FileInformation fileinfo,
            DokanFileInfo info)
        {
            if (filename == RootFolder || info.IsDirectory)
            {
                var folder = (filename == RootFolder) ? Root : Root.GetFolderByPath(filename);
                if (!folder.Exists())
                    return -DokanNet.ERROR_PATH_NOT_FOUND;

                fileinfo.FileName = folder.Name;
                fileinfo.Attributes = folder.Attributes;
                fileinfo.LastAccessTime = folder.LastAccessTime;
                fileinfo.LastWriteTime = folder.LastWriteTime;
                fileinfo.CreationTime = folder.CreationTime;
                return DokanNet.DOKAN_SUCCESS;
            }
            var name = filename.GetFilenamePart();
            var parentFolder = Root.GetFolderByPath(filename.GetPathPart());

            if (!parentFolder.Exists())
                return DokanNet.DOKAN_ERROR;

            var file = parentFolder.FetchFile(name);
            if (!file.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;

            fileinfo.FileName = file.Name;
            fileinfo.Attributes = file.Attributes;
            fileinfo.LastAccessTime = file.LastAccessTime;
            fileinfo.LastWriteTime = file.LastWriteTime;
            fileinfo.CreationTime = file.CreationTime;
            fileinfo.Length = file.Size; 
            return DokanNet.DOKAN_SUCCESS;
        }

        public int LockFile(
            string filename,
            long offset,
            long length,
            DokanFileInfo info)
        {
            return DokanNet.DOKAN_SUCCESS;
        }

        public int MoveFile(
            string filename,
            string newname,
            bool replace,
            DokanFileInfo info)
        {
            var newParent = Root.GetFolderByPath(newname.GetPathPart());

            if (!newParent.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            var testNewFolder1 = Root.GetFolderByPath(newname);
            if (testNewFolder1.Exists())
                return -DokanNet.ERROR_ALREADY_EXISTS;

            var testNewFile = newParent.FetchFile(newname.GetFilenamePart());
            if (testNewFile.Exists())
                return -DokanNet.ERROR_FILE_EXISTS;

            var newName = newname.GetFilenamePart();
            if (string.IsNullOrEmpty(newName))
                return -DokanNet.ERROR_INVALID_NAME;

            if (info.IsDirectory)
            {
                var sourceFolder = Root.GetFolderByPath(filename);
                if (!sourceFolder.Exists())
                    return -DokanNet.ERROR_FILE_NOT_FOUND;
                sourceFolder.Parent = newParent;
                sourceFolder.Name = newName;
                return DokanNet.DOKAN_SUCCESS;
            }
            var name = filename.GetFilenamePart();
            var parentFolder = Root.GetFolderByPath(filename.GetPathPart());
            if (!parentFolder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            var thisFile = parentFolder.FetchFile(name);
            if (!thisFile.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;
            thisFile.Parent = newParent;
            thisFile.Name = newName;

            return DokanNet.DOKAN_SUCCESS;
        }

        public int OpenDirectory(string filename, DokanFileInfo info)
        {
            if (filename == RootFolder)
                return DokanNet.DOKAN_SUCCESS;

            var testFolder = Root.GetFolderByPath(filename);
            if (!testFolder.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND; //#46

            return DokanNet.DOKAN_SUCCESS;
        }

        public int ReadFile(
            string filename,
            byte[] buffer,
            ref uint readBytes,
            long offset,
            DokanFileInfo info)
        {        	
            var parentFolder = Root.GetFolderByPath(filename.GetPathPart());

            if (!parentFolder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;
              
            var name = filename.GetFilenamePart();
            var file = parentFolder.FetchFile(name);

            if (!file.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;

            readBytes = file.Read(offset, buffer);
            return DokanNet.DOKAN_SUCCESS;
        }

        public int WriteFile(string filename, byte[] buffer, ref uint writtenBytes,
             long offset, DokanFileInfo info)
        {
            var parentFolder = Root.GetFolderByPath(filename.GetPathPart());

            if (!parentFolder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            var name = filename.GetFilenamePart();
            var file = parentFolder.FetchFile(name);

            if (!file.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;

            if (offset + buffer.Length > file.Size)
            	file.Size = offset + buffer.Length;
                        
            writtenBytes = file.Write(offset, buffer);
            return DokanNet.DOKAN_SUCCESS;
        }

        public int SetEndOfFile(string filename, long length, DokanFileInfo info)
        {
            var parentFolder = Root.GetFolderByPath(filename.GetPathPart());

            if (!parentFolder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            var name = filename.GetFilenamePart();
            var file = parentFolder.FetchFile(name);

            if (!file.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;

           	file.Size = length;
            return DokanNet.DOKAN_SUCCESS;
        }

        public int SetAllocationSize(string filename, long length, DokanFileInfo info)
        {
            var parentFolder = Root.GetFolderByPath(filename.GetPathPart());

            if (!parentFolder.Exists())
                return -DokanNet.ERROR_PATH_NOT_FOUND;

            var name = filename.GetFilenamePart();
            var file = parentFolder.FetchFile(name);

            if (!file.Exists())
                return -DokanNet.ERROR_FILE_NOT_FOUND;

			file.Size = length;
            return DokanNet.DOKAN_SUCCESS;
        }

        public int SetFileAttributes(
            string filename,
            FileAttributes attr,
            DokanFileInfo info)
        {
            return -DokanNet.DOKAN_ERROR;
        }

        public int SetFileTime(
            string filename,
            DateTime ctime,
            DateTime atime,
            DateTime mtime,
            DokanFileInfo info)
        {
            return -DokanNet.DOKAN_ERROR;
        }

        public int UnlockFile(string filename, long offset, long length, DokanFileInfo info)
        {
            return DokanNet.DOKAN_SUCCESS;
        }

        public int Unmount(DokanFileInfo info)
        {
            return DokanNet.DOKAN_SUCCESS;
        }

        public void Initialize(IWaitScreenService waitScreenService)
        {
            WaitScreenService = waitScreenService;
            lock (DatabaseManager.LockObject)
            {
                Root = CreateRoot();
                foreach (var item in DatabaseManager.Instance.GetRootLibraryItems())
                {
                   Load(new MemoryFolder(Root, item.Title) {Tag = item});
                }
            }
        }

        private void Load(MemoryFolder folder)
        {
            WaitScreenService.AdvanceProgress();
            var libraryItem = (LibraryItem)folder.Tag;
            foreach (var child in libraryItem.Children.Where(c=>c.IsEmpty()==false))
            {
                var childFolder = new MemoryFolder(folder, child.Title) {Tag = child};
                Load(childFolder);
            }
            foreach (var movie in libraryItem.Movies)
            {
                var actualFileName = movie.GetFileToPlay();
                if(string.IsNullOrEmpty(actualFileName) ==false && File.Exists(actualFileName))
                {
                    MediaFile.Create(folder, actualFileName).Tag=movie;
                }
            }
            foreach (var music in libraryItem.Musics)
            {
                var actualFileName = music.FullPath;
                if (string.IsNullOrEmpty(actualFileName) == false && File.Exists(actualFileName))
                {
                    MediaFile.Create(folder, actualFileName).Tag=music;
                }
            }
        }


        public int GetDiskFreeSpace(
            ref ulong freeBytesAvailable,
            ref ulong totalBytes,
            ref ulong totalFreeBytes,
            DokanFileInfo info)
        {
            freeBytesAvailable = 512 * 1024 * 1024;
            totalBytes = 1024 * 1024 * 1024;
            totalFreeBytes = 512 * 1024 * 1024;
            return DokanNet.DOKAN_SUCCESS;
        }

        #endregion
    }
}
